package argparse

import (
	"fmt"
	"os"
	"reflect"
	"sort"
	"strconv"
	"strings"
)

type ArgumentParser struct {
	Cmd  string
	Args []string

	help  *Option
	fOpts map[string]*Option // flag argument options
	pOpts []*Option          // positional argument options

	pos int // positional argument index

	rv reflect.Value
	fm map[string]bool // fill-map indicates if a key is (fully) filled
}

func New(v interface{}) (*ArgumentParser, error) {
	ap := &ArgumentParser{
		Cmd:  os.Args[0],
		Args: nil,

		fOpts: make(map[string]*Option),
		pOpts: make([]*Option, 0),

		pos: 0,

		rv: reflect.Value{},
		fm: make(map[string]bool),
	}

	ap.rv = reflect.ValueOf(v)
	if ap.rv.IsNil() {
		return nil, fmt.Errorf("unmarshal nil")
	}
	if ap.rv.Kind() != reflect.Ptr {
		return nil, fmt.Errorf("unmarshal non-pointer type %s", ap.rv.Type())
	}

	ap.help = &Option{
		Name:     "__help__",
		Short:    'h',
		Long:     "help",
		Required: false,
		Type:     nil,
		NArgs:    NArgsNone,
		Help:     "show this message and exit",
	}

	vt := reflect.Indirect(ap.rv).Type()
	for i := 0; i < vt.NumField(); i++ {
		field := vt.Field(i)
		tag, ok := field.Tag.Lookup("argparse")
		if !ok {
			continue
		}
		opt, err := parseOption(field.Name, tag, field.Type)
		if err != nil {
			return nil, err
		}
		if opt.isPositional() {
			ap.pOpts = append(ap.pOpts, opt)
		} else {
			ap.fOpts[field.Name] = opt
		}
	}
	return ap, nil
}

func (ap *ArgumentParser) addValue(opt *Option, v string) error {
	k := opt.Name
	field := reflect.Indirect(ap.rv).FieldByName(k)
	switch k := opt.Type.Kind(); k {
	case reflect.Bool:
		x, err := strconv.ParseBool(v)
		if err != nil {
			return err
		}
		field.SetBool(x)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		x, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return err
		}
		field.SetUint(x)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		x, err := strconv.ParseInt(v, 10, 64)
		if err != nil {
			return err
		}
		field.SetInt(x)
	case reflect.Float32, reflect.Float64:
		x, err := strconv.ParseFloat(v, 64)
		if err != nil {
			return err
		}
		field.SetFloat(x)
	case reflect.String:
		field.SetString(v)
	default:
		return fmt.Errorf("invalid value type: `%s'", k)
	}
	ap.fm[k] = false
	return nil
}

func (ap *ArgumentParser) filled(k string) bool {
	_, ok := ap.fm[k]
	return ok
}

// func (ap *ArgumentParser) fullyFilled(k string) bool { // TODO support nargs
// 	ff, ok := ap.fm[k]
// 	return ok && ff
// }

func (ap *ArgumentParser) parseArgs(a []string) error {
	ap.Args = make([]string, len(a))
	copy(ap.Args, a)

	for _, s := range a {
		if s == fmt.Sprintf("-%c", ap.help.Short) || s == fmt.Sprintf("--%s", ap.help.Long) {
			ap.PrintUsage()
			os.Exit(0)
		}
	}

	j := 0
	for j < len(a) {
		opt, err := ap.search(a[j])
		if err != nil {
			return err
		}

		// TODO apply arguments: support nargs
		switch na := opt.NArgs; na {
		case NArgsNone:
			ok, _ := strconv.ParseBool(opt.Default)
			if ok {
				ap.addValue(opt, "false")
			} else {
				ap.addValue(opt, "true")
			}
		case NArgsOne:
			if ap.filled(opt.Name) {
				break
			}
			if j+1 == len(a) {
				return fmt.Errorf("wrong arguments of `%s'", opt.Name)
			}
			j++
			ap.addValue(opt, a[j])
		case NArgsAtMostOne:
			fallthrough
		case NArgsAtLeastOne:
			fallthrough
		case NArgsAny:
			return fmt.Errorf("%s has not been supported yet", na)
		default:
			return fmt.Errorf("invalid nargs value: %d", na)
		}
		j++
	}

	for name, opt := range ap.fOpts {
		if name == "" {
			continue
		}
		if opt.Default != "" {
			ap.addValue(opt, opt.Default)
			delete(ap.fOpts, name)
		} else if opt.Required {
			return fmt.Errorf("unmet required argument: %s", opt.Name)
		}
	}

	if pOpts := ap.pOpts[ap.pos:]; len(pOpts) != 0 {
		return fmt.Errorf("unmet required positional argument: %v", pOpts[0].Name)
	}

	return nil
}

func (ap *ArgumentParser) search(key string) (*Option, error) {
	if strings.HasPrefix(key, "--") {
		var k, v string
		if strings.ContainsRune(key, '=') {
			parts := strings.SplitN(key[2:], "=", 2)
			k, v = parts[0], parts[1]
		} else {
			k = key[2:]
		}
		for name, opt := range ap.fOpts {
			if opt.Long == k {
				if v != "" {
					if opt.NArgs == NArgsOne {
						ap.addValue(opt, v)
						// ap.fk[opt.Name] = true
					} else {
						return nil, fmt.Errorf("invalid k=v format `%s' for a non-value argument", key)
					}
				}
				delete(ap.fOpts, name)
				return opt, nil
			}
		}
		return nil, fmt.Errorf("unknown long flag `%s'", key)
	}

	if strings.HasPrefix(key, "-") {
		if len(key) != 2 {
			return nil, fmt.Errorf("invalid short flag `%s'", key)
		}
		k := key[1]
		for name, opt := range ap.fOpts {
			if opt.Short == k {
				delete(ap.fOpts, name)
				return opt, nil
			}
		}
		return nil, fmt.Errorf("unknown short flag `%s'", key)
	}

	if ap.pos < len(ap.pOpts) {
		opt := ap.pOpts[ap.pos]
		ap.addValue(opt, key)
		ap.pos++
		return opt, nil
	}
	return nil, fmt.Errorf("positional argument (%d) not found", ap.pos)
}

func (ap *ArgumentParser) Parse() error {
	return ap.parseArgs(os.Args[1:])
}

func (ap *ArgumentParser) ParseArgs(a []string) error {
	return ap.parseArgs(a)
}

func optionToUsage(opt *Option) string {
	var s string
	if opt.Short != 0 {
		s += fmt.Sprintf("    -%c", opt.Short)
	}
	if opt.Long != "" {
		if s != "" {
			s += ","
		} else {
			s += "    "
		}
		s += fmt.Sprintf("--%s", opt.Long)
	}
	if s != "" {
		s += "\t"
	} else {
		s += "    "
	}
	if opt.Name == "__help__" {
		s += opt.Help
	} else {
		s += fmt.Sprintf("%s\t%s", opt.Name, opt.Help)
	}
	if opt.Required {
		s += " (required)"
	} else if opt.Default != "" {
		s += fmt.Sprintf(" (default: %s)", opt.Default)
	}
	return s
}

func (ap *ArgumentParser) usage() string {
	var s string = fmt.Sprintf("Usage of %s:\n%s\n\n", ap.Cmd, optionToUsage(ap.help))
	if len(ap.fOpts) > 0 {
		names := make([]string, 0, len(ap.fOpts))
		for name := range ap.fOpts {
			names = append(names, name)
		}
		sort.Strings(names)
		s += "Options:\n"
		for _, name := range names {
			s += fmt.Sprintln(optionToUsage(ap.fOpts[name]))
		}
	}
	if len(ap.pOpts) > 0 {
		if len(ap.fOpts) > 0 {
			s += "\n"
		}
		s += "Positional Arguments:\n"
		for _, opt := range ap.pOpts {
			s += fmt.Sprintln(optionToUsage(opt))
		}
	}
	return s
}

func (ap *ArgumentParser) SetUsageFlag(short byte, long string) {
	ap.help.Short = short
	ap.help.Long = long
}

func (ap *ArgumentParser) PrintUsage() {
	fmt.Print(ap.usage())
}

func Parse(v interface{}) error {
	ap, err := New(v)
	if err != nil {
		return err
	}
	return ap.Parse()
}

func ParseArgs(v interface{}, a []string) error {
	ap, err := New(v)
	if err != nil {
		return err
	}
	return ap.ParseArgs(a)
}
